package com.citywithincity.db.meta.impl.mysql;

import java.sql.Blob;
import java.sql.Types;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import com.citywithincity.db.Db;
import com.citywithincity.db.Record;
import com.citywithincity.db.drivers.mysql.MysqlDriver;
import com.citywithincity.db.interfaces.SqlDriver;
import com.citywithincity.db.meta.ColumnStruct;
import com.citywithincity.db.meta.DbStructFactory;
import com.citywithincity.db.meta.KeyType;
import com.citywithincity.db.meta.impl.AbsDbStructFactory;
import com.citywithincity.db.meta.impl.DbStruct;

public class MysqlDbStruct extends AbsDbStructFactory implements DbStructFactory {

	private String dbName;

	public MysqlDbStruct(SqlDriver driver, String dbName) {
		super(driver);
		this.dbName = dbName;
	}

	private int convertTypeToInt(String type) {
		if (type.equals("varchar")) {
			return Types.VARCHAR;
		} else if (type.equals("int")) {
			return Types.INTEGER;
		} else if (type.contains("text")) {
			return Types.CLOB;
		} else if (type.contains("blob")) {
			return Types.BLOB;
		} else if (type.contains("date")) {
			return Types.DATE;
		} else if (type.equals("datetime")) {
			return Types.DATE;
		} else if (type.equals("time")) {
			return Types.TIME;
		} else if (type.equals("double")) {
			return Types.DOUBLE;
		} else if (type.equals("tinyint")) {
			return Types.BOOLEAN;
		} else if (type.equals("smallint")) {
			return Types.SMALLINT;
		} else if (type.equals("mediumint")) {
			return Types.INTEGER;
		} else if (type.equals("bigint")) {
			return Types.BIGINT;
		} else if (type.equals("decimal")) {
			return Types.DECIMAL;
		} else if (type.equals("timestamp")) {
			return Types.TIMESTAMP;
		} else if (type.equals("year")) {
			return Types.DATE;
		} else if (type.equals("char")) {
			return Types.CHAR;
		}
		throw new RuntimeException("Not supported type " + type);
	}

	private static Class<?> convertType(String type) {
		if (type.equals("varchar")) {
			return String.class;
		} else if (type.equals("int")) {
			return Integer.class;
		} else if (type.contains("text")) {
			return String.class;
		} else if (type.contains("blob")) {
			return Blob.class;
		} else if (type.contains("date")) {
			return java.sql.Date.class;
		} else if (type.equals("datetime")) {
			return java.sql.Date.class;
		} else if (type.equals("time")) {
			return java.sql.Time.class;
		} else if (type.equals("double")) {
			return Double.class;
		} else if (type.equals("tinyint")) {
			return Boolean.class;
		} else if (type.equals("smallint")) {
			return Short.class;
		} else if (type.equals("mediumint")) {
			return Integer.class;
		} else if (type.equals("bigint")) {
			return Long.class;
		} else if (type.equals("decimal")) {
			return Double.class;
		} else if (type.equals("timestamp")) {
			return Integer.class;
		} else if (type.equals("year")) {
			return Integer.class;
		} else if (type.equals("char")) {
			return String.class;
		}
		throw new RuntimeException("Not supported type " + type);
	}

	/**
	 * 字段的注释
	 */

	protected Map<String, String> getTableCommentMap() {
		List<Record> list = Db.ar(getDb()).select("table_comment,table_name").where("table_schema", dbName)
				.get("information_schema.tables");

		Map<String, String> result = new HashMap<String, String>();
		for (Record data : list) {
			result.put(data.getString("table_name").toUpperCase(), data.getString("table_comment"));
		}

		return result;
	}

	private Map<String, List<Record>> columnsMap;

	protected Map<String, List<Record>> getColumns() {
		Map<String, List<Record>> columnsMap = this.columnsMap;
		if (columnsMap == null) {
			columnsMap = new HashMap<String, List<Record>>(100);
			List<Record> columns = Db.ar(getDb())
					.select("TABLE_NAME,COLUMN_NAME,IS_NULLABLE,DATA_TYPE,CHARACTER_MAXIMUM_LENGTH,COLUMN_KEY,EXTRA,COLUMN_COMMENT,COLUMN_DEFAULT")
					.where("table_schema", dbName).get("information_schema.columns");

			for (Record record : columns) {
				String table = record.getString("TABLE_NAME").toUpperCase();
				List<Record> l = columnsMap.get(table);
				if (l == null) {
					l = new ArrayList<Record>();
					columnsMap.put(table, l);
				}
				l.add(record);
			}
			this.columnsMap = columnsMap;
		}
		return columnsMap;

	}

	@Override
	protected List<ColumnStruct> getColumnStructs(String tableName) {
		List<Record> list = getColumns().get(tableName.toUpperCase());
		if (list == null) {
			throw new RuntimeException("找不到表" + tableName);
		}
		List<ColumnStruct> structs = new ArrayList<ColumnStruct>();
		int index = 0;
		for (Record record : list) {
			structs.add(parse(record, tableName, index++));
		}
		return structs;
	}

	private ColumnStruct parse(Record record, String tableName, int index) {

		ColumnStruct struct = new ColumnStruct(tableName);
		struct.setIndex(index);
		struct.setName(record.getString("COLUMN_NAME"));
		struct.setComment(record.getString("COLUMN_COMMENT"));
		// 常用的
		struct.setAuto(record.getString("EXTRA").equals("auto_increment"));
		String key = record.getString("COLUMN_KEY");
		if (key.equals("PRI")) {
			struct.setKeyType(KeyType.Primary);
		} else if (key.equals("UNI")) {
			struct.setKeyType(KeyType.Unique);
		} else if (key.equals("MUL")) {
			struct.setKeyType(KeyType.Index);
		}
		struct.setDefaultValue(record.getString("COLUMN_DEFAULT"));
		struct.setNullable(record.getString("IS_NULLABLE").equals("YES"));
		struct.setMaxLen(record.getLong("CHARACTER_MAXIMUM_LENGTH"));
		struct.setClazz(convertType(record.getString("DATA_TYPE")));
		struct.setRawType(record.getString("DATA_TYPE"));
		struct.setType(convertTypeToInt(record.getString("DATA_TYPE")));
		return struct;
	}

	protected Map<String, List<ColumnStruct>> createColumnMap() {
		List<Record> list = Db.ar(getDb())
				.select("TABLE_NAME,COLUMN_NAME,IS_NULLABLE,DATA_TYPE,CHARACTER_MAXIMUM_LENGTH,COLUMN_KEY,EXTRA,COLUMN_COMMENT,COLUMN_DEFAULT")
				.where("table_schema", dbName).get("information_schema.columns");
		// 将每一个表的数据分解出来
		// Types.DATE;
		Map<String, List<ColumnStruct>> map = new HashMap<String, List<ColumnStruct>>(300);
		for (Record record : list) {
			String orgTableName = record.getString("TABLE_NAME");
			String tableName = orgTableName.toUpperCase();

			ColumnStruct struct = new ColumnStruct(orgTableName);
			struct.setName(record.getString("COLUMN_NAME"));
			struct.setComment(record.getString("COLUMN_COMMENT"));
			// 常用的
			struct.setAuto(record.getString("EXTRA").equals("auto_increment"));
			String key = record.getString("COLUMN_KEY");
			if (key.equals("PRI")) {
				struct.setKeyType(KeyType.Primary);
			} else if (key.equals("UNI")) {
				struct.setKeyType(KeyType.Unique);
			} else if (key.equals("MUL")) {
				struct.setKeyType(KeyType.Index);
			}
			struct.setDefaultValue(record.getString("COLUMN_DEFAULT"));
			struct.setNullable(record.getString("IS_NULLABLE").equals("YES"));
			struct.setMaxLen(record.getLong("CHARACTER_MAXIMUM_LENGTH"));
			struct.setClazz(convertType(record.getString("DATA_TYPE")));
			struct.setRawType(record.getString("DATA_TYPE"));
			struct.setType(convertTypeToInt(record.getString("DATA_TYPE")));

			List<ColumnStruct> l = map.get(tableName);
			if (l == null) {
				l = new ArrayList<ColumnStruct>();
				map.put(tableName, l);
			}
			l.add(struct);
		}
		return map;
	}

	@Override
	protected String getDbType(ColumnStruct struct) {
		switch (struct.getType()) {
		case Types.INTEGER:
			return "int(32)";
		case Types.SMALLINT:
			return "smallint(8)";
		case Types.BIGINT:
			return "bigint(64)";
		case Types.VARCHAR:
			return new StringBuilder().append("varchar(").append(struct.getMaxLen()).append(")").toString();
		case Types.NVARCHAR:
			return new StringBuilder().append("varchar(").append(struct.getMaxLen()).append(")").toString();
		case Types.DATE:
			return "date";
		case Types.TIME:
			return "time";
		case Types.BOOLEAN:
			return "tinyint(1)";
		case Types.TIMESTAMP:
			return "timestamp";
		case Types.CHAR:
			return new StringBuilder().append("char(").append(struct.getMaxLen()).append(")").toString();
		case Types.CLOB:
			if (struct.getMaxLen() == 65535) {
				return new StringBuilder().append("text").toString();
			}
			if (struct.getMaxLen() == 0) {
				return new StringBuilder().append("text").toString();
			}
		case Types.NUMERIC:
			return "int(32)";
		case Types.DOUBLE:
			return "double";
		case Types.BLOB:
			return "blob";
		default:
			throw new RuntimeException("不支持的类型" + struct.getType());
		}

	}

	@Override
	public DbStruct getDbStruct() {
		// TODO Auto-generated method stub
		return null;
	}

}
